home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / ASM8086.ZIP / ASM8086.TUT next >
Text File  |  1995-05-15  |  66KB  |  1,326 lines

  1.                IBM Personal Computer Assembly
  2.                  Language Tutorial
  3.  
  4.                   Joshua Auerbach
  5.                   Yale University
  6.                 Yale Computer Center
  7.                  175 Whitney Avenue
  8.                    P. O. Box 2112
  9.             New Haven, Connecticut 06520
  10.                 Installation Code YU
  11.            Integrated Personal Computers Project
  12.                 Communications Group
  13.            Communications and Data Base Division
  14.                 Session C316
  15.  
  16.   This talk is for people who are just getting started with the PC MACRO
  17. Assembler.  Maybe you are just contemplating doing some coding in assembler,
  18. maybe you have tried it with mixed success.  If you are here to get aimed in
  19. the right direction, to get off to a good start with the assembler, then you
  20. have come for the right reason.  I can't promise you'll get what you want, but
  21. I'll do my best.
  22.  
  23.   On the other hand, if you have already turned out some working assembler
  24. code, then this talk is likely to be on the elementary side for you.  If you
  25. want to review a few basics and have no where else pressing to go, then by all
  26. means stay.
  27.  
  28.   Why Learn Assembler?
  29.   ____________________
  30.  
  31.   The reasons for LEARNING assembler are not the same as the reasons for USING
  32. it in a particular application.  But, we have to start with some of the reasons
  33. for using it and then I think the reasons for learning it will become clear.
  34.  
  35.   First, let's dispose of a bad reason for using it.  Don't use it just because
  36. you think it is going to execute faster.  A particular sequence of ordinary
  37. bread-and-butter computations written in PASCAL, C, FORTRAN, or compiled BASIC
  38. can do the job just about as fast as the same algorithm coded in assembler.  Of
  39. course, interpretive BASIC is slower, but if you have a BASIC application which
  40. runs too slow you probably want to try compiling it before you think too much
  41. about translating parts of it to another language.
  42.  
  43.   On the other hand, high level languages do tend to isolate you from the
  44. machine.  That is both their strength and their weakness.  Usually, when
  45. implemented on a micro, a high level language provides an escape mechanism to
  46. the underlying operating system or to the bare machine.  So, for example, BASIC
  47. has its PEEK and POKE.    But, the route to the bare machine is often a
  48. circuitous one, leading to tricky programming which is hard to follow.
  49.  
  50.   For those of us working on PC's connected to SHARE-class mainframes, we are
  51. generally concerned with three interfaces:  the keyboard, the screen, and the
  52. communication line or lines.  All three of these entities raise machine
  53. dependent issues which are imperfectly addressed by the underlying operating
  54. system or by high level languages.
  55.  
  56.   Sometimes, the system or the language does too little for you.  For example,
  57. with the asynch adapter, the system provides no interrupt handler, no buffer,
  58. and no flow control.  The application is stuck with the responsibility for
  59. monitoring that port and not missing any characters, then deciding what to do
  60. with all errors.  BASIC does a reasonable job on some of this, but that is only
  61. BASIC.    Most other languages do less.  Sometimes, the system may do too much
  62. for you.  System support for the keyboard is an example.  At the hardware
  63. level, all 83 keys on the keyboard send unique codes when they are pressed,
  64. held down, and released.  But, someone has decided that certain keys, like Num
  65. Lock and Scroll Lock are going to do certain things before the application even
  66. sees them and can't therefore be used as ordinary keys.
  67.  
  68.   Sometimes, the system does about the right amount of stuff but does it less
  69. efficiently then it should.  System support for the screen is in this class.
  70. If you use only the official interface to the screen you sometimes slow your
  71. application down unacceptably.    I said before, don't use assembler just to
  72. speed things up, but there I was talking about mainline code, which generally
  73. can't be speeded up much by assembler coding.  A critical system interface is a
  74. different matter:  sometimes we may have to use assembler to bypass a
  75. hopelessly inefficient implementation.    We don't want to do this if we can
  76. avoid it, but sometimes we can't.
  77.  
  78.   Assembly language code can overcome these deficiencies.  In some cases, you
  79. can also overcome these deficiencies by judicious use of the escape valves
  80. which your high level language provides.  In BASIC, you can PEEK and POKE and
  81. INP and OUT your way around a great many issues.  In many other languages you
  82. can issue system calls and interrupts and usually manage, one way or other, to
  83. modify system memory.  Writing handlers to take real-time hardware interrupts
  84. from the keyboard or asynch port, though, is still going to be a problem in
  85. most languages.  Some languages claim to let you do it but I have yet to see an
  86. acceptably clean implementation done that way.
  87.  
  88.   The real reason while assembler is better than "tricky POKEs" for writing
  89. machine-dependent code, though, is the same reason why PASCAL is better than
  90. assembler for writing a payroll package:  it is easier to maintain.  Let the
  91. high level language do what it does best, but recognize that there are some
  92. things which are best done in assembler code.  The assembler, unlike the tricky
  93. POKE, can make judicious use of equates, macros, labels, and appropriately
  94. placed comments to show what is really going on in this machine-dependent realm
  95. where it thrives.
  96.  
  97.   So, there are times when it becomes appropriate to write in assembler; given
  98. that, if you are a responsible programmer or manager, you will want to be
  99. "assembler-literate" so you can decide when assembler code should be written.
  100.  
  101.   What do I mean by "assembler-literate?" I don't just mean understanding the
  102. 8086 architecture; I think, even if you don't write much assembler code
  103. yourself, you ought to understand the actual process of turning out assembler
  104. code and the various ways to incorporate it into an application.  You ought to
  105. be able to tell good assembler code from bad, and appropriate assembler code
  106. from inappropriate.
  107.  
  108. Steps to becoming ASSEMBLER-LITERATE
  109. ____________________________________
  110.  
  111.   1.  Learn the 8086 architecture and most of the instruction set.  Learn what
  112. you need to know and ignore what you don't.  Reading:  The 8086 Primer by
  113. Stephen Morse, published by Hayden.  You need to read only two chapters, the
  114. one on machine organization and the one on the instruction set.
  115.  
  116.   2.  Learn about a few simple DOS function calls.  Know what services the
  117. operating system provides.  If appropriate, learn a little about other systems
  118. too.  It will aid portability later on.  Reading:  appendices D and E of the PC
  119. DOS manual.
  120.  
  121.   3.  Learn enough about the MACRO assembler and the LINKer to write some
  122. simple things that really work.  Here, too, the main thing is figuring out what
  123. you don't need to know.  Whatever you do, don't study the sample programs
  124. distributed with the assembler unless you have nothing better!
  125.  
  126.   4.  At the same time as you are learning the assembler itself, you will need
  127. to learn a few tools and concepts to properly combine your assembler code with
  128. the other things you do.  If you plan to call assembler subroutines from a high
  129. level language, you will need to study the interface notes provided in your
  130. language manual.  Usually, this forms an appendix of some sort.  If you plan to
  131. package your assembler routines as .COM programs you will need to learn to do
  132. this.  You should also learn to use DEBUG.
  133.  
  134.   5.  Read the Technical Reference, but very selectively.  The most important
  135. things to know are the header comments in the BIOS listing.  Next, you will
  136. want to learn about the RS 232 port and maybe about the video adapters.
  137.  
  138.   Notice that the key thing in all five phases is being selective.  It is easy
  139. to conclude that there is too much to learn unless you can throw away what you
  140. don't need.  Most of the rest of this talk is going to deal with this very
  141. important question of what you need and don't need to learn in each phase.  In
  142. some cases, I will have to leave you to do almost all of the learning, in
  143. others, I will teach a few salient points, enough, I hope, to get you started.
  144. I hope you understand that all I can do in an hour is get you started on the
  145. way.
  146.  
  147.   Phase 1:  Learn the architecture and instruction set
  148.  ____________________________________________________
  149.  
  150.   The Morse book might seem like a lot of book to buy for just two really
  151. important chapters; other books devote a lot more space to the instruction set
  152. and give you a big beautiful reference page on each instruction.  And, some of
  153. the other things in the Morse book, although interesting, really aren't very
  154. vital and are covered too sketchily to be of any real help.  The reason I like
  155. the Morse book is that you can just read it; it has a very conversational
  156. style, it is very lucid, it tells you what you really need to know, and a
  157. little bit more which is by way of background; because nothing really gets
  158. belabored to much, you can gracefully forget the things you don't use.  And, I
  159. very much recommend READING Morse rather than studying it.  Get the big picture
  160. at this point.
  161.  
  162.   Now, you want to concentrate on those things which are worth fixing in
  163. memory.  After you read Morse, you should relate what you have learned to this
  164. outline.
  165.  
  166.   1.  You want to fix in your mind the idea of the four segment registers CODE,
  167. DATA, STACK, and EXTRA.  This part is pretty easy to grasp.  The 8086 and the
  168. 8088 use 20 bit addresses for memory, meaning that they can address up to 1
  169. megabyte of memory.  But, the registers and the address fields in all the
  170. instructions are no more that 16 bits long.  So, how to address all of that
  171. memory?  Their solution is to put together two 16 bit quantities like this:
  172.  
  173.       calculation  SSSS0   ---- value in the relevant segment register SHL 4
  174.       depicted in   AAAA   ---- apparent address from register or instruction
  175.       hexadecimal --------
  176.            RRRRR   ---- real address placed on address bus
  177.  
  178.   In other words, any time memory is accessed, your program will supply a
  179. sixteen bit address.  Another sixteen bit address is acquired from a segment
  180. register, left shifted four bits (one nibble) and added to it to form the real
  181. address.  You can control the values in the segment registers and thus access
  182. any part of memory you want.  But the segment registers are specialized:  one
  183. for code, one for most data accesses, one for the stack (which we'll mention
  184. again) and one "extra" one for additional data accesses.
  185.  
  186.   Most people, when they first learn about this addressing scheme become
  187. obsessed with converting everything to real 20 bit addresses.  After a while,
  188. though, you get use to thinking in segment/offset form.  You tend to get your
  189. segment registers set up at the beginning of the program, change them as little
  190. as possible, and think just in terms of symbolic locations in your program, as
  191. with any assembly language.
  192.  
  193.       EXAMPLE:
  194.        MOV    AX,DATASEG
  195.        MOV    DS,AX           ;Set value of Data segment
  196.        ASSUME DS:DATASEG   ;Tell assembler DS is usable
  197.        .......
  198.        MOV    AX,PLACE       ;Access storage symbolically by 16 bit address
  199.  
  200.   In the above example, the assembler knows that no special issues are involved
  201. because the machine generally uses the DS register to complete a normal data
  202. reference.
  203.  
  204.   If you had used ES instead of DS in the above example, the assembler would
  205. have known what to do, also.  In front of the MOV instruction which accessed
  206. the location PLACE, it would have placed the ES segment prefix.  This would
  207. tell the machine that ES should be used, instead of DS, to complete the
  208. address.
  209.  
  210.   Some conventions make it especially easy to forget about segment registers.
  211. For example, any program of the COM type gets control with all four segment
  212. registers containing the same value.  This program executes in a simplified 64K
  213. address space.    You can go outside this address space if you want but you don't
  214. have to.
  215.  
  216.   2.  You will want to learn what other registers are available and learn their
  217. personalities:
  218.  
  219.   AX and DX are general purpose registers.  They become special only when
  220. accessing machine and system interfaces.
  221.  
  222.   CX is a general purpose register which is slightly specialized for counting.
  223.  
  224.   BX is a general purpose register which is slightly specialized for forming
  225. base-displacement addresses.
  226.  
  227.   AX-DX can be divided in half, forming AH, AL, BH, BL, CH, CL, DH, DL.
  228.  
  229.   SI and DI are strictly 16 bit.  They can be used to form indexed addresses
  230. (like BX) and they are also used to point to strings.
  231.  
  232.   SP is hardly ever manipulated.  It is there to provide a stack.
  233.  
  234.   BP is a manipulable cousin to SP.  Use it to access data which has been
  235. pushed onto the stack.
  236.  
  237.   Most sixteen bit operations are legal (even if unusual) when performed in SI,
  238. DI, SP, or BP.
  239.  
  240.   3.  You will want to learn the classifications of operations available
  241. WITHOUT getting hung up in the details of how 8086 opcodes are constructed.
  242.  
  243.   8086 opcodes are complex.  Fortunately, the assembler opcodes used to
  244. assemble them are simple.  When you read a book like Morse, you will learn some
  245. things which are worth knowing but NOT worth dwelling on.
  246.  
  247.   a.  8086 and 8088 instructions can be broken up into subfields and bits with
  248. names like R/M, MOD, S and W.  These parts of the instruction modify the basic
  249. operation in such ways as whether it is 8 bit or 16 bit, if 16 bit, whether all
  250. 16 bits of the data are given, whether the instruction is register to register,
  251. register to memory, or memory to register, for operands which are registers,
  252. which register, for operands which are memory, what base and index registers
  253. should be used in finding the data.
  254.  
  255.   b.  Also, some instructions are actually represented by several different
  256. machine opcodes depending on whether they deal with immediate data or not, or
  257. on other issues, and there are some expedited forms which assume that one of
  258. the arguments is the most commonly used operand, like AX in the case of
  259. arithmetic.
  260.  
  261.   There is no point in memorizing any of this detail; just distill the bottom
  262. line, which is, what kinds of operand combinations EXIST in the instruction set
  263. and what kinds don't.  If you ask the assembler to ADD two things and the two
  264. things are things for which there is a legal ADD instruction somewhere in the
  265. instruction set, the assembler will find the right instruction and fill in all
  266. the modifier fields for you.
  267.  
  268.   I guess if you memorized all the opcode construction rules you might have a
  269. crack at being able to disassemble hex dumps by eye, like you may have learned
  270. to do somewhat with 370 assembler.  I submit to you that this feat, if ever
  271. mastered by anyone, would be in the same class as playing the "Minute Waltz" in
  272. a minute; a curiosity only.  Here is the basic matrix you should remember:
  273.  
  274.      Two operands:        One operand:
  275.       R <-- M         R
  276.       M <-- R         M
  277.       R <-- R         S *
  278.       R|M <-- I
  279.       R|M <-- S  *
  280.       S <-- R|M  *
  281.  
  282.       * -- data moving instructions (MOV, PUSH, POP) only
  283.       S -- segment register (CS, DS, ES, SS)
  284.       R -- ordinary register (AX, BX, CX, DX, SI, DI, BP, SP,
  285.                   AH, AL, BH, BL, CH, CL, DH, DL)
  286.       M -- one of the following
  287.            pure address
  288.            [BX]+offset
  289.            [BP]+offset
  290.            any of the above indexed by SI
  291.            any of the first three indexed by DI
  292.  
  293.   4.  Of course, you want to learn the operations themselves.  As I've
  294. suggested, you want to learn the op codes as the assembler presents them, not
  295. as the CPU machine language presents them.  So, even though there are many MOV
  296. op codes you don't need to learn them.  Basically, here is the instruction set:
  297.  
  298.   a.  Ordinary two operand instructions.  These instructions perform an
  299. operation and leave the result in place of one of the operands.  They are:
  300.  
  301.   1) ADD and ADC -- addition, with or without including a carry from a previous
  302. addition 2) SUB and SBB -- subtraction, with or without including a borrow from
  303. a previous subtraction 3) CMP -- compare.  It is useful to think of this as a
  304. subtraction with the answer being thrown away and neither operand actually
  305. changed 4) AND, OR, XOR -- typical boolean operations 5) TEST -- like an AND,
  306. except the answer is thrown away and neither operand is changed.  6) MOV --
  307. move data from source to target 7) LDS, LES, LEA -- some specialized forms of
  308. MOV with side effects
  309.  
  310.   b.  Ordinary one operand instructions.  These can take any of the operand
  311. forms described above.    Usually, the perform the operation and leave the result
  312. in the stated place:
  313.  
  314.   1) INC -- increment contents
  315.   2) DEC -- decrement contents
  316.   3)  NEG -- twos complement
  317.   4)  NOT -- ones complement
  318.   5)  PUSH -- value goes on stack (operand location itself unchanged)
  319.   6)  POP -- value taken from stack, replaces current value
  320.  
  321.   c.  Now you touch on some instructions which do not follow the general
  322. operand rules but which require the use of certain registers.  The important
  323. ones are:
  324.  
  325.   1) The multiply and divide instructions 2) The "adjust" instructions which
  326. help in performing arithmetic on ASCII or packed decimal data 3) The shift and
  327. rotate instructions.  These have a restriction on the second operand:  it must
  328. either be the immediate value 1 or the contents of the CL register.  4) IN and
  329. OUT which send or receive data from one of the 1024 hardware ports.  5) CBW and
  330. CWD -- convert byte to word or word to doubleword by sign extension
  331.  
  332.   d.  Flow of control instructions.  These deserve study in themselves and we
  333. will discuss them a little more.  They include:
  334.  
  335.     1)  CALL, RET -- call and return
  336.     2)  INT, IRET -- interrupt and return-from-interrupt
  337.     3)  JMP -- jump or "branch"
  338.     4)  LOOP, LOOPNZ, LOOPZ -- special (and useful) instructions which
  339.         implement a counted loop similar to the 370 BCT instruction
  340.     5)  various conditional jump instructions
  341.  
  342.   e.  String instructions.  These implement a limited storage-to-storage
  343. instruction subset and are quite powerful.  All of them have the property that:
  344.  
  345.   1) The source of data is described by the combination DS and SI.
  346.  
  347.   2) The destination of data is described by the combination ES and DI.
  348.  
  349.   3) As part of the operation, the SI and/or DI register(s) is(are) incremented
  350. or decremented so the operation can be repeated.
  351.  
  352.   They include:
  353.  
  354.     1)  CMPSB/CMPSW -- compare byte or word
  355.     2)  LODSB/LODSW -- load byte or word into AL or AX
  356.     3)  STOSB/STOSW -- store byte or word from AL or AX
  357.     4)  MOVSB/MOVSW -- move byte or word
  358.     5)  SCASB/SCASW -- compare byte or word with contents of AL or AX
  359.     6)  REP/REPE/REPNE -- a prefix which can be combined with any of
  360.         the above instructions to make them execute repeatedly across a
  361.         string of data whose length is held in CX.
  362.  
  363.     f.    Flag instructions: CLI, STI, CLD, STD, CLC, STC.  These can set or
  364.     clear the interrupt (enabled) direction (for string operations) or
  365.     carry flags.
  366.  
  367.  
  368.  
  369.   The addressing summary and the instruction summary given above masks a lot of
  370. annoying little exceptions.  For example, you can't POP CS, and although the R
  371. <-- M form of LES is legal, the M <-- R form isn't etc.  etc.  My advice is:
  372.  
  373.     a.    Go for the general rules
  374.  
  375.     b.    Don't try to memorize the exceptions
  376.  
  377.     c.    Rely on common sense and the assembler to teach you about
  378.     exceptions over time.  A lot of the exceptions cover things you
  379.     wouldn't want to do anyway.
  380.  
  381.   5.  A few instructions are rich enough and useful enough to warrent careful
  382. study.    Here are a few final study guidelines:
  383.  
  384.   a.  It is well worth the time learning to use the string instruction set
  385. effectively.  Among the most useful are:
  386.  
  387.         REP MOVSB        ;moves a string
  388.         REP STOSB        ;initializes memory
  389.         REPNE SCASB        ;look up occurance of character in string
  390.         REPE CMPSB        ;compare two strings
  391.  
  392.   b.  Similarly, if you have never written for a stack machine before, you will
  393. need to exercise PUSH and POP and get very comfortable with them because they
  394. are going to be good friends.  If you are used to the 370, with lots of general
  395. purpose registers, you may find yourself feeling cramped at first, with many
  396. fewer registers and many instructions having register restrictions.  But, you
  397. have a hidden ally:  you need a register and you don't want to throw away
  398. what's in it?  Just PUSH it, and when you are done, POP it back.  This can lead
  399. to abuse.  Never have more than two "expedient" PUSHes in effect and never
  400. leave something PUSHed across a major header comment or for more than 15
  401. instructions or so.  An exception is the saving and restoring of registers at
  402. entrance to and exit from a subroutine; here, if the subroutine is long, you
  403. should probably PUSH everything which the caller may need saved, whether you
  404. will use the register or not, and POP it in reverse order at the end.  Be aware
  405. that CALL and INT push return address information on the stack and RET and IRET
  406. pop it off.  It is a good idea to become familiar with the structure of the
  407. stack.
  408.  
  409.   c.  In practice, to invoke system services you will use the INT instruction.
  410. It is quite possible to use this instruction effectively in a cookbook fashion
  411. without knowing precisely how it works.
  412.  
  413.   d.  The transfer of control instructions (CALL, RET, JMP) deserve careful
  414. study to avoid confusion.  You will learn that these can be classified as
  415. follows:
  416.  
  417.   1) all three have the capability of being either NEAR (CS register unchanged)
  418. or FAR (CS register changed)
  419.  
  420.   2) JMPs and CALLs can be DIRECT (target is assembled into instruction) or
  421. INDIRECT (target fetched from memory or register)
  422.  
  423.   3) if NEAR and DIRECT, a JMP can be SHORT (less than 128 bytes away) or LONG
  424.  
  425.   In general, the third issue is not worth worrying about.  On a forward jump
  426. which is clearly VERY short, you can tell the assembler it is short and save
  427. one byte of code:
  428.  
  429.            JMP SHORT  CLOSEBY
  430.  
  431.   On a backward jump, the assembler can figure it out for you.    On a forward
  432. jump of dubious length, let the assembler default to a LONG form; at worst you
  433. waste one byte.
  434.  
  435.   Also leave the assembler to worry about how the target address is to be
  436. represented, in absolute form or relative form.
  437.  
  438.   e.  The conditional jump set is rather confusing when studied apart from the
  439. assembler, but you do need to get a feeling for it.  The interactions of the
  440. sign, carry, and overflow flags can get your mind stuttering pretty fast if you
  441. worry about it too much.  What is boils down to, though, is
  442.  
  443.         JZ      means what it says
  444.         JNZ      means what it says
  445.         JG reater this means "if the SIGNED difference is positive"
  446.         JA bove   this means "if the UNSIGNED difference is positive"
  447.         JL ess      this means "if the SIGNED difference is negative"
  448.         JB elow   this means "if the UNSIGNED difference is negative"
  449.         JC arry   assembles the same as JB; it's an aesthetic choice
  450.  
  451.   You should understand that all conditional jumps are inherently DIRECT, NEAR,
  452. and "short"; the "short" part means that they can't go more than 128 bytes in
  453. either direction.  Again, this is something you could easily imagine to be more
  454. of a problem than it is.  I follow this simple approach:
  455.  
  456.   1) When taking an abnormal exit from a block of code, I always use an
  457. unconditional jump.  Who knows how far you are going to end up jumping by the
  458. time the program is finished.  For example, I wouldn't code this:
  459.  
  460.            TEST     AL,IDIBIT        ;Is the idiot bit on?
  461.            JNZ        OYVEY        ;Yes.  Go to general cleanup
  462.  
  463.         Rather, I would probably code this:
  464.  
  465.            TEST     AL,IDIBIT        ;Is the idiot bit on?
  466.            JZ        NOIDIOCY        ;No.  I am saved.
  467.            JMP        OYVEY        ;Yes.  What can we say...
  468.           NOIDIOCY:
  469.  
  470.   The latter, of course, is a jump around a jump.  Some would say it is evil,
  471. but I submit it is hard to avoid in this language.
  472.  
  473.   2) Otherwise, within a block of code, I use conditional jumps freely.  If the
  474. block eventually grows so long that the assembler starts complaining that my
  475. conditional jumps are too long I:
  476.  
  477.   a) consider reorganizing the block but
  478.  
  479.   b) also consider changing some conditional jumps to their opposite and use
  480. the "jump around a jump" approach as shown above.
  481.  
  482.     Enough about specific instructions!
  483.  
  484.   6.  Finally, in order to use the assembler effectively, you need to know the
  485. default rules for which segment registers are used to complete addresses in
  486. which situations.
  487.  
  488.   a.  CS is used to complete an address which is the target of a NEAR DIRECT
  489. jump.  On an NEAR INDIRECT jump, DS is used to fetch the address from memory
  490. but then CS is used to complete the address thus fetched.  On FAR jumps, of
  491. course, CS is itself altered.  The instruction counter is always implicitly
  492. pointing in the code segment.
  493.  
  494.   b.  SS is used to complete an address if BP is used in its formation.
  495. Otherwise, DS is always used to complete a data address.
  496.  
  497.   c.  On the string instructions, the target is always formed from ES and DI.
  498. The source is normally formed from DS and SI.  If there is a segment prefix, it
  499. overrides the source not the target.
  500.  
  501. Learning about DOS
  502. __________________
  503.  
  504.   I think the best way to learn about DOS internals is to read the technical
  505. appendices in the manual.  These are not as complete as we might wish, but they
  506. really aren't bad; I certainly have learned a lot from them.  What you don't
  507. learn from them you might eventually learn via judicious disassembly of parts
  508. of DOS, but that shouldn't really be necessary.
  509.  
  510.   From reading the technical appendices, you learn that interrupts 20H through
  511. 27H are used to communicate with DOS.  Mostly, you will use interrupt 21H, the
  512. DOS function manager.
  513.  
  514.   The function manager implements a great many services.  You request the
  515. individual services by means of a function code in the AH register.  For
  516. example, by putting a nine in the AH register and issuing interrupt 21H you
  517. tell DOS to print a message on the console screen.
  518.  
  519.   Usually, but by no means always, the DX register is used to pass data for the
  520. service being requested.  For example, on the print message service just
  521. mentioned, you would put the 16 bit address of the message in the DX register.
  522. The DS register is also implicitly part of this argument, in keeping with the
  523. universal segmentation rules.
  524.  
  525.   In understanding DOS functions, it is useful to understand some history and
  526. also some of the philosophy of MS-DOS with regard to portability.  Generally,
  527. you will find, once you read the technical information on DOS and also the IBM
  528. technical reference, you will know more than one way to do almost anything.
  529. Which is best?    For example, to do asynch adapter I/O, you can use the DOS
  530. calls (pretty incomplete), you can use BIOS, or you can go directly to the
  531. hardware.  The same thing is true for most of the other primitive I/O (keyboard
  532. or screen) although DOS is more likely to give you added value in these areas.
  533. When it comes to file I/O, DOS itself offers more than one interface.  For
  534. example, there are four calls which read data from a file.
  535.  
  536.   The way to decide rationally among these alternatives is by understanding the
  537. tradeoffs of functionality versus portability.    Three kinds of portability need
  538. to be considered:  machine portability, operating system portability (for
  539. example, the ability to assemble and run code under CP/M 86) and DOS version
  540. portability (the ability for a program to run under older versions of DOS>.
  541.  
  542.   Most of the functions originally offered in DOS 1.0 were direct descendents
  543. of CP/M functions; there is even a compatibility interface so that programs
  544. which have been translated instruction for instruction from 8080 assembler to
  545. 8086 assembler might have a reasonable chance of running if they use only the
  546. core CP/M function set.  Among the most generally useful in this original
  547. compatibility set are:
  548.  
  549.   09   --  print a full message on the screen
  550.   0A   --  get a console input line with full DOS editing
  551.   0F   --  open a file
  552.   10   --  close a file (really needed only when writing)
  553.   11   --  find first file matching a pattern
  554.   12   --  find next file matching a pattern
  555.   13   --  erase a file
  556.   16   --  create a file
  557.   17   --  rename a file
  558.   1A   --  set disk transfer address
  559.  
  560.   The next set provide no function above what you can get with BIOS calls or
  561. more specialized DOS calls.  However, they are preferable to BIOS calls when
  562. portability is an issue.
  563.  
  564.   00   --  terminate execution
  565.   01   --  read keyboard character
  566.   02   --  write screen character
  567.   03   --  read COM port character
  568.   04   --  write COM port character
  569.   05   --  print a character
  570.   06   --  read keyboard or write screen with no editing
  571.  
  572.   The standard file I/O calls are inferior to the specialized DOS calls but
  573. have the advantage of making the program easier to port to CP/M style systems.
  574. Thus they are worth mentioning:
  575.  
  576.   14   --  sequential read from file
  577.   15   --  sequential write to file
  578.   21   --  random read from file
  579.   22   --  random write to file
  580.   23   --  determine file size
  581.   24   --  set random record
  582.  
  583.   In addition to the CP/M compatible services, DOS also offers some specialized
  584. services which have been available in all releases of DOS.  These include:
  585.  
  586.   27   --  multi-record random read.
  587.   28   --  multi-record random write.
  588.   29   --  parse filename
  589.   2A-2D -- get and set date and time
  590.  
  591.   All of the calls mentioned above which have anything to do with files make
  592. use of a data area called the "FILE CONTROL BLOCK" (FCB).  The FCB is anywhere
  593. from 33 to 37 bytes long depending on how it is used.  You are responsible for
  594. creating an FCB and filling in the first 12 bytes, which contain a drive code,
  595. a file name, and an extension.
  596.  
  597.   When you open the FCB, the system fills in the next 20 bytes, which includes
  598. a logical record length.  The initial lrecl is always 128 bytes, to achieve
  599. CP/M compatibility.  The system also provides other useful information such as
  600. the file size.
  601.  
  602.   After you have opened the FCB, you can change the logical record length.  If
  603. you do this, your program is no longer CP/M compatible, but that doesn't make
  604. it a bad thing to do.  DOS documentation suggests you use a logical record
  605. length of one for maximum flexibility.    This is usually a good recommendation.
  606.  
  607.   To perform actual I/O to a file, you eventually need to fill in byte 33 or
  608. possibly bytes 34-37 of the FCB.  Here you supply information about the record
  609. you are interested in reading or writing.  For the most part, this part of the
  610. interface is compatible with CP/M.
  611.  
  612.   In general, you do not need to (and should not) modify other parts of the
  613. FCB.
  614.  
  615.   The FCB is pretty well described in appendix E of the DOS manual.  Beginning
  616. with DOS 2.0, there is a whole new system of calls for managing files which
  617. don't require that you build an FCB at all.  These calls are quite incompatible
  618. with CP/M and also mean that your program cannot run under older releases of
  619. DOS.  However, these calls are very nice and easy to use.  They have these
  620. characteristics
  621.  
  622.   1.  To open, create, delete, or rename a file, you need only a character
  623. string representing its name.
  624.  
  625.   2.  The open and create calls return a 16 bit value which is simply placed in
  626. the BX register on subsequent calls to refer to the file.
  627.  
  628.   3.  There is not a separate call required to specify the data buffer.
  629.  
  630.   4.  Any number of bytes can be transfered on a single call; no data area must
  631. be manipulated to do this.
  632.  
  633.   The "new" DOS calls also include comprehensive functions to manipulate the
  634. new chained directory structure and to allocate and free memory.
  635.  
  636. Learning the assembler
  637. ______________________
  638.  
  639.   It is my feeling that many people can teach themselves to use the assembler
  640. by reading the MACRO Assembler manual if:
  641.  
  642.   1.  You have read and understood a book like Morse and thus have a feeling
  643. for the instruction set
  644.  
  645.   2.  You know something about DOS services and so can communicate with the
  646. keyboard and screen and do something marginally useful with files.  In the
  647. absence of this kind of knowledge, you can't write meaningful practice programs
  648. and so will not progress.
  649.  
  650.   3.  You have access to some good examples (the ones supplied with the
  651. assembler are not good, in my opinion.    I will try to supply you with some more
  652. relevant ones.
  653.  
  654.   4.  You ignore the things which are most confusing and least useful.    Some of
  655. the most confusing aspects of the assembler include the facilities combining
  656. segments.  But, you can avoid using all but the simplest of these facilities in
  657. many cases, even while writing quite substantial applications.
  658.  
  659.   5.  The easiest kind of assembler program to write is a COM program.    They
  660. might seem harder, at first, then EXE programs because there is an extra step
  661. involved in creating the executable file, but COM programs are structurally
  662. very much simpler.
  663.  
  664.   At this point, it is necessary to talk about COM programs and EXE programs.
  665. As you probably know, DOS supports two kinds of executable files.  EXE programs
  666. are much more general, can contain many segments, and are generally built by
  667. compilers and sometimes by the assembler.  If you follow the lead given by the
  668. samples distributed with the assembler, you will end up with EXE programs.  A
  669. COM program, in contrast, always contains just one segment, and receives
  670. control with all four segment registers containing the same value.  A COM
  671. program, thus, executes in a simplified environment, a 64K address space.  You
  672. can go outside this address space simply by temporarily changing one segment
  673. register, but you don't have to, and that is the thing which makes COM programs
  674. nice and simple.  Let's look at a very simple one.
  675.  
  676.   The classic text on writing programs for the C language says that the first
  677. thing you should write is a program which says
  678.  
  679.   HELLO, WORLD.
  680.  
  681.   when invoked.  What's sauce for C is sauce for assembler, so let's start with
  682. a HELLO program of our own.  My first presentation of this will be bare bones,
  683. not stylistically complete, but just an illustration of what an assembler
  684. program absolutely has to have:
  685.  
  686. HELLO  SEGMENT            ;Set up HELLO code and data section
  687.        ASSUME CS:HELLO,DS:HELLO ;Tell assembler about conditions at entry
  688.        ORG  100H        ;A .COM program begins with 100H byte prefix
  689. MAIN:  JMP  BEGIN        ;Control must start here
  690. MSG    DB   'Hello, world.$'    ;But it is generally useful to put data first
  691. BEGIN: MOV  DX,OFFSET MSG    ;Let DX --> message.
  692.        MOV  AH,9        ;Set DOS function code for printing a message
  693.        INT  21H         ;Invoke DOS
  694.        RET            ;Return to system
  695. HELLO  ENDS            ;End of code and data section
  696.        END  MAIN        ;Terminate assembler and specify entry point
  697.  
  698.   First, let's attend to some obvious points.  The macro assembler uses the
  699. general form
  700.  
  701.    name    opcode    operands
  702.  
  703.   Unlike the 370 assembler, though, comments are NOT set off from operands by
  704. blanks.  The syntax uses blanks as delimiters within the operand field (see
  705. line 6 of the example) and so all comments must be set off by semi-colons.
  706. Line comments are frequently set off with a semi-colon in column 1.  I use this
  707. approach for block comments too, although there is a COMMENT statement which
  708. can be used to introduce a block comment.
  709.  
  710.   Being an old 370 type, I like to see assembler code in upper case, although
  711. my comments are mixed case.  Actually, the assembler is quite happy with mixed
  712. case anywhere.
  713.  
  714.   As with any assembler, the core of the opcode set consists of opcodes which
  715. generate machine instructions but there are also opcodes which generate data
  716. and ones which function as instructions to the assembler itself, sometimes
  717. called pseudo-ops.  In the example, there are five lines which generate machine
  718. code (JMP, MOV, MOV, INT, RET), one line which generates data (DB) and five
  719. pseudo-ops (SEGMENT, ASSUME, ORG, ENDS, and END).
  720.  
  721.   We will discuss all of them.
  722.  
  723.   Now, about labels.  You will see that some labels in the example end in a
  724. colon and some don't.  This is just a bit confusing at first, but no real
  725. mystery.  If a label is attached to a piece of code (as opposed to data), then
  726. the assembler needs to know what to do when you JMP to or CALL that label.  By
  727. convention, if the label ends in a colon, the assembler will use the NEAR form
  728. of JMP or CALL.  If the label does not end in a colon, it will use the FAR
  729. form.  In practice, you will always use the colon on any label you are jumping
  730. to inside your program because such jumps are always NEAR; there is no reason
  731. to use a FAR jump within a single code section.  I mention this, though,
  732. because leaving off the colon isn't usually trapped as a syntax error, it will
  733. generally cause something more abstruse to go wrong.
  734.  
  735.   On the other hand, a label attached to a piece of data or a pseudo-op never
  736. ends in a colon.
  737.  
  738.   Machine instructions will generally take zero, one or two operands.  Where
  739. there are two operands, the one which receives the result goes on the left as
  740. in 370 assembler.
  741.  
  742.   I tried to explain this before, now maybe it will be even clearer:  there are
  743. many more 8086 machine opcodes then there are assembler opcodes to represent
  744. them.  For example, there are five kinds of JMP, four kinds of CALL, two kinds
  745. of RET, and at least five kinds of MOV depending on how you count them.  The
  746. macro assembler makes a lot of decisions for you based on the form taken by the
  747. operands or on attributes assigned to symbols elsewhere in your program.  In
  748. the example above, the assembler will generate the NEAR DIRECT form of JMP
  749. because the target label BEGIN labels a piece of code instead of a piece of
  750. data (this makes the JMP DIRECT) and ends in a colon (this makes the JMP NEAR).
  751. The assembler will generate the immediate forms of MOV because the form OFFSET
  752. MSG refers to immediate data and because 9 is a constant.  The assembler will
  753. generate the NEAR form of RET because that is the default and you have not told
  754. it otherwise.
  755.  
  756.   The DB (define byte) pseudo-op is an easy one:  it is used to put one or more
  757. bytes of data into storage.  There is also a DW (define word) pseudo-op and a
  758. DD (define doubleword) pseudo-op; in the PC MACRO assembler, the fact that a
  759. label refers to a byte of storage, a word of storage, or a doubleword of
  760. storage can be very significant in ways which we will see presently.
  761.  
  762.   About that OFFSET operator, I guess this is the best way to make the point
  763. about how the assembler decides what instruction to assemble:  an analogy with
  764. 370 assembler:
  765.  
  766.   PLACE    DC    ......
  767.        ...
  768.        LA    R1,PLACE
  769.        L    R1,PLACE
  770.  
  771.   In 370 assembler, the first instruction puts the address of label PLACE in
  772. register 1, the second instruction puts the contents of storage at label PLACE
  773. in register 1.    Notice that two different opcodes are used.  In the PC
  774. assembler, the analogous instructions would be:
  775.  
  776.   PLACE    DW    ......
  777.        ...
  778.        MOV    DX,OFFSET PLACE
  779.        MOV    DX,PLACE
  780.  
  781.   If PLACE is the label of a word of storage, then the second instruction will
  782. be understood as a desire to fetch that data into DX.  If X is a label, then
  783. "OFFSET X" means "the ordinary number which represents X's offset from the
  784. start of the segment." And, if the assembler sees an ordinary number, as
  785. opposed to a label, it uses the instruction which is equivalent to LA.
  786.  
  787. If PLACE were the label of a DB pseudo-op, instead of a DW, then
  788.  
  789.        MOV    DX,PLACE
  790.  
  791.   would be illegal.  The assembler worries about length attributes of its
  792. operands.
  793.  
  794.   Next, numbers and constants in general.  The assembler's default radix is
  795. decimal.  You can change this, but I don't recommend it.  If you want to
  796. represent numbers in other forms of notation such as hex or bit, you generally
  797. use a trailing letter.    For example,
  798.  
  799.        21H
  800.   is hexidecimal 21,
  801.        00010000B
  802.   is the eight bit binary number pictured.
  803.  
  804.  
  805.  
  806.   The next elements we should point to are the SEGMENT...ENDS pair and the END
  807. instruction.  Every assembler program has to have these elements.  SEGMENT
  808. tells the assembler you are starting a section of contiguous material (code
  809. and/or data).  The symmetrically named ENDS statement tells the assembler you
  810. are finished with a section of contiguous material.  I wish they didn't use the
  811. word SEGMENT in this context.  To me, a "segment" is a hardware construct:  it
  812. is the 64K of real storage which becomes addressable by virtue of having a
  813. particular value in a segment register.  Now, it is true that the "segments"
  814. you make with the assembler often correspond to real hardware "segments" at
  815. execution time.  But, if you look at things like the GROUP and CLASS options
  816. supported by the linker, you will discover that this correspondence is by no
  817. means exact.  So, at risk of maybe confusing you even more, I am going to use
  818. the more informal term "section" to refer to the area set off by means of the
  819. SEGMENT and ENDS instructions.    The sections delimited by SEGMENT...ENDS pairs
  820. are really a lot like CSECTs and DSECTs in the 370 world.
  821.  
  822.   I strongly recommend that you be selective in your study of the SEGMENT
  823. pseudo-op as described in the manual.  Let me just touch on it here.
  824.  
  825.   name       SEGMENT
  826.   name       SEGMENT  PUBLIC
  827.   name       SEGMENT  AT    nnn
  828.  
  829.   Basically, you can get away with just the three forms given above.  The first
  830. form is what you use when you are writing a single section of assembler code
  831. which will not be combined with other pieces of code at link time.  The second
  832. form says that this assembly only contains part of the section; other parts
  833. might be assembled separately and combined later by the linker.
  834.  
  835.   I have found that one can construct reasonably large modular applications in
  836. assembler by simply making every assembly use the same segment name and
  837. declaring the name to be PUBLIC each time.  If you read the assembler and
  838. linker documentation, you will also be bombarded by information about more
  839. complex options such as the GROUP statement and the use of other "combine
  840. types" and "classes." I don't recommend getting into any of that.  I will talk
  841. more about the linker and modular construction of programs a little later.  The
  842. assembler manual also implies that a STACK segment is required.  This is not
  843. really true.  There are numerous ways to assure that you have a valid stack at
  844. execution time.
  845.  
  846.   Of course, if you plan to write applications in assembler which are more than
  847. 64K in size, you will need more than what I have told you; but who is really
  848. going to do that?  Any application that large is likely to be coded in a higher
  849. level language.
  850.  
  851.   The third form of the SEGMENT statement makes the delineated section into
  852. something like a "DSECT;" that is, it doesn't generate any code, it just
  853. describes what is present somewhere already in the computer's memory.
  854. Sometimes the AT value you give is meaningful.    For example, the BIOS work area
  855. is located at location 40 hex.    So, you might see
  856.  
  857.   BIOSAREA  SEGMENT AT 40H    ;Map BIOS work area
  858.         ORG  BIOSAREA+10H
  859.   EQUIP     DB     ?        ;Location of equipment flags, first byte
  860.   BIOSAREA  ENDS
  861.  
  862.   in a program which was interested in mucking around in the BIOS work area.
  863. At other times, the AT value you give may be arbitrary, as when you are mapping
  864. a repeated control block:
  865.  
  866.   PROGPREF SEGMENT   AT 0      ;Really a DSECT mapping the program prefix
  867.        ORG     PROGPREF+6
  868.   MEMSIZE  DW    ?           ;Size of available memory
  869.   PROGPREF ENDS
  870.  
  871.   Really, no matter whether the AT value represents truth or fiction, it is
  872. your responsibility, not the assembler's, to get set up a segment register so
  873. that you can really reach the storage in question.  So, you can't say
  874.  
  875.      MOV  AL,EQUIP
  876.  
  877. unless you first say something like
  878.  
  879.      MOV  AX,BIOSAREA   ;BIOSAREA becomes a symbol with value 40H
  880.      MOV  ES,AX
  881.      ASSUME ES:BIOSAREA
  882.  
  883. Enough about SEGMENT.  The END statement is simple.  It goes at the end of
  884. every assembly.  When you are assembling a subroutine, you just say
  885.  
  886.      END
  887.  
  888. but when you are assembling the main routine of a program you say
  889.  
  890.     END label
  891.  
  892. where 'label' is the place where execution is to begin.
  893.  
  894.   Another pseudo-op illustrated in the program is ASSUME.  ASSUME is like the
  895. USING statement in 370 assembler.  However, ASSUME can ONLY refer to segment
  896. registers.  The assembler uses ASSUME information to decide whether to assemble
  897. segment override prefixes and to check that the data you are trying to access
  898. is really accessible.  In this case, we can reassure the assembler that both
  899. the CS and DS registers will address the section called HELLO at execution
  900. time.  Actually, the SS and ES registers will too, but the assembler never
  901. needs to make use of this information.
  902.  
  903.   I guess I have explained everything in the program except that ORG pseudo-op.
  904. ORG means the same thing as it does in many assembly languages.  It tells the
  905. assembler to move its location counter to some particular address.  In this
  906. case, we have asked the assembler to start assembling code hex 100 bytes from
  907. the start of the section called HELLO instead of at the very beginning.  This
  908. simply reflects the way COM programs are loaded.  When a COM program is loaded
  909. by the system, the system sets up all four segment registers to address the
  910. same 64K of storage.  The first 100 hex bytes of that storage contains what is
  911. called the program prefix; this area is described in appendix E of the DOS
  912. manual.  Your COM program physically begins after this.  Execution begins with
  913. the first physical byte of your program; that is why the JMP instruction is
  914. there.
  915.  
  916.   Wait a minute, you say, why the JMP instruction at all?  Why not put the data
  917. at the end?  Well, in a simple program like this I probably could have gotten
  918. away with that.  However, I have the habit of putting data first and would
  919. encourage you to do the same because of the way the assembler has of assembling
  920. different instructions depending on the nature of the operand.    Unfortunately,
  921. sometimes the different choices of instruction which can assemble from a single
  922. opcode have different lengths.    If the assembler has already seen the data when
  923. it gets to the instructions it has a good chance of reserving the right number
  924. of bytes on the first pass.  If the data is at the end, the assembler may not
  925. have enough information on the first pass to reserve the right number of bytes
  926. for the instruction.  Sometimes the assembler will complain about this,
  927. something like "Forward reference is illegal" but at other times, it will make
  928. some default assumption.  On the second pass, if the assumption turned out to
  929. be wrong, it will report what is called a "Phase error," a very nasty error to
  930. track down.  So get in the habit of putting data and equated symbols ahead of
  931. code.
  932.  
  933.   OK.  Maybe you understand the program now.  Let's walk through the steps
  934. involved in making it into a real COM file.
  935.  
  936.   1.  The file should be created with the name HELLO.ASM (actually the name is
  937. arbitrary but the extension .ASM is conventional and useful)
  938.  
  939.   2.  Now type:
  940.  
  941.   ASM HELLO,,;
  942.  
  943.   (this is just one example of invoking the assembler; it uses the small
  944. assembler ASM, it produces an object file and a listing file with the same name
  945. as the source file.  I am not going exhaustively into how to invoke the
  946. assembler, which the manual goes into pretty well.  I guess this is the first
  947. time I mentioned that there are really two assemblers; the small assembler ASM
  948. will run in a 64K machine and doesn't support macros.  I used to use it all the
  949. time; now that I have a bigger machine and a lot of macro libraries I use the
  950. full function assembler MASM.  You get both when you buy the package).
  951.  
  952.   3.  If you issue DIR at this point, you will discover that you have acquired
  953. HELLO.OBJ (the object code resulting from the assembly) and HELLO.LST (a
  954. listing file).    I guess I can digress for a second here concerning the listing
  955. file.  It contains TAB characters.  I have found there are two good ways to get
  956. it printed and one bad way.  The bad way is to use LPT1:  as the direct target
  957. of the listing file or to try copying the LST file to LPT1 without first
  958. setting the tabs on the printer.  The two good ways are to either:
  959.  
  960.   a.  direct it to the console and activate the printer with CTRL-PRTSC.  In
  961. this case, DOS will expand the tabs for you.
  962.  
  963.   b.  direct to LPT1:  but first send the right escape sequence to LPT1 to set
  964. the tabs every eight columns.  I have found that on some early serial numbers
  965. of the IBM PC printer, tabs don't work quite right, which forces you to the
  966. first option.
  967.  
  968. 4.  Now type:
  969.  
  970.       LINK    HELLO;
  971.  
  972.   (again, there are lots of linker options but this is the simplest.  It takes
  973. HELLO.OBJ and makes HELLO.EXE).  HELLO.EXE?  I thought we were making a COM
  974. program, not an EXE program.  Right.  HELLO.EXE isn't really executable; its
  975. just that the linker doesn't know about COM programs.  That requires another
  976. utility.  You don't have this utility if you are using DOS 1.0; you have it if
  977. you are using DOS 1.1 or DOS 2.0.  Oh, by the way, the linker will warn you
  978. that you have no stack segment.  Don't worry about it.
  979.  
  980. 5.  Now type:
  981.  
  982.       EXE2BIN  HELLO HELLO.COM
  983.  
  984.   This is the final step.  It produces the actual program you will execute.
  985. Note that you have to spell out HELLO.COM; for a nominally rational but
  986. actually perverse reason, EXE2BIN uses the default extension BIN instead of COM
  987. for its output file.  At this point, you might want to erase HELLO.EXE; it
  988. looks a lot more useful than it is.  Chances are you won't need to recreate
  989. HELLO.COM unless you change the source and then you are going to have to redo
  990. the whole thing.
  991.  
  992. 6.  Now type:
  993.  
  994.       HELLO
  995.  
  996.     You type hello, that invokes the program, it says
  997.  
  998.       HELLO YOURSELF!!!
  999.  
  1000.     (oops, what did I do wrong....?)
  1001.  
  1002. What about subroutines?
  1003. _______________________
  1004.  
  1005.   I started with a simple COM program because I actually think they are easier
  1006. to create than subroutines to be called from high level languages, but maybe
  1007. its really the latter you are interested in.  Here, I think you should get
  1008. comfortable with the assembler FIRST with little exercises like the one above
  1009. and also another one which I will finish up with.
  1010.  
  1011.   Next you are ready to look at the interface information for your particular
  1012. language.  You usually find this in some sort of an appendix.  For example, the
  1013. BASIC manual has Appendix C on Machine Language Subroutines.  The PASCAL manual
  1014. buries the information a little more deeply:  the interface to a separately
  1015. compiled routine can be found in the Chapter on Procedures and Functions, in a
  1016. subsection called Internal Calling Conventions.
  1017.  
  1018.   Each language is slightly different, but here are what I think are some
  1019. common issues in subroutine construction.
  1020.  
  1021.   1.  NEAR versus FAR?    Most of the time, your language will probably call your
  1022. assembler routine as a FAR routine.  In this case, you need to make sure the
  1023. assembler will generate the right kind of return.  You do this with a
  1024. PROC...ENDP statement pair.  The PROC statement is probably a good idea for a
  1025. NEAR routine too even though it is not strictly required:
  1026.  
  1027.           FAR linkage:      |           NEAR linkage:
  1028.                   |
  1029.     ARBITRARY SEGMENT          |  SPECIFIC  SEGMENT    PUBLIC
  1030.           PUBLIC THENAME      |           PUBLIC THENAME
  1031.           ASSUME CS:ARBITRARY |           ASSUME CS:SPECIFIC,DS:SPECIFIC
  1032.     THENAME   PROC FAR          |           ASSUME ES:SPECIFIC,SS:SPECIFIC
  1033.           ..... code and data |  THENAME   PROC NEAR
  1034.     THENAME   ENDP          |           ..... code and data ....
  1035.     ARBITRARY ENDS          |  THENAME   ENDP
  1036.           END          |  SPECIFIC  ENDS
  1037.                   |           END
  1038.  
  1039.   With FAR linkage, it doesn't really matter what you call the segment.  you
  1040. must declare the name by which you will be called in a PUBLIC pseudo-op and
  1041. also show that it is a FAR procedure.  Only CS will be initialized to your
  1042. segment when you are called.  Generally, the other segment registers will
  1043. continue to point to the caller's segments.  With NEAR linkage, you are
  1044. executing in the same segment as the caller.  Therefore, you must give the
  1045. segment a specific name as instructed by the language manual.  However, you may
  1046. be able to count on all segment registers pointing to your own segment
  1047. (sometimes the situation can be more complicated but I cannot really go into
  1048. all of the details).  You should be aware that the code you write will not be
  1049. the only thing in the segment and will be physically relocated within the
  1050. segment by the linker.    However, all OFFSET references will be relocated and
  1051. will be correct at execution time.
  1052.  
  1053.   2.  Parameters passed on the stack.  Usually, high level languages pass
  1054. parameters to subroutines by pushing words onto the stack prior to calling you.
  1055. What may differ from language to language is the nature of what is pushed
  1056. (OFFSET only or OFFSET and SEGMENT) and the order in which it is pushed (left
  1057. to right, right to left within the CALL statement).  However, you will need to
  1058. study the examples to figure out how to retrieve the parameters from the stack.
  1059. A useful fact to exploit is the fact that a reference involving the BP register
  1060. defaults to a reference to the stack segment.  So, the following strategy can
  1061. work:
  1062.  
  1063.       ARGS     STRUC
  1064.            DW   3 DUP(?)  ;Saved BP and return address
  1065.       ARG3     DW   ?
  1066.       ARG2     DW   ?
  1067.       ARG1     DW   ?
  1068.       ARGS     ENDS
  1069.        ...........
  1070.            PUSH BP               ;save BP register
  1071.            MOV  BP,SP           ;Use BP to address stack
  1072.            MOV   ...,[BP].ARG2     ;retrieve second argument
  1073.            (etc.)
  1074.  
  1075.   This example uses something called a structure, which is only available in
  1076. the large assembler; furthermore, it uses it without allocating it, which is
  1077. not a well-documented option.  However, I find the above approach generally
  1078. pleasing.  The STRUC is like a DSECT in that it establishes labels as being
  1079. offset a certain distance from an arbitrary point; these labels are then used
  1080. in the body of code by beginning them with a period; the construction ".ARG2"
  1081. means, basically, "+(ARG2-ARGS)."
  1082.  
  1083.   What you are doing here is using BP to address the stack, accounting for the
  1084. word where you saved the caller's BP and also for the two words which were
  1085. pushed by the CALL instruction.
  1086.  
  1087.   3.  How big is the stack?  BASIC only gives you an eight word stack to play
  1088. with.  On the other hand, it doesn't require you to save any registers except
  1089. the segment registers.    Other languages give you a liberal stack, which makes
  1090. things a lot easier.  If you have to create a new stack segment for yourself,
  1091. the easiest thing is to place the stack at the end of your program and:
  1092.  
  1093.      CLI              ;suppress interrupts while changing the stack
  1094.      MOV  SSAVE,SS          ;save old SS in local storage (old SP
  1095.                   ; already saved in BP)
  1096.      MOV  SP,CS          ;switch
  1097.      MOV  SS,SP          ;the
  1098.      MOV  SP,OFFSET STACKTOP  ;stack
  1099.      STI              ;(maybe)
  1100.  
  1101.   Later, you can reverse these steps before returning to the caller.  At the
  1102. end of your program, you place the stack itself:
  1103.  
  1104.          DW   128 DUP(?)          ;stack of 128 words (liberal)
  1105.     STACKTOP LABEL WORD
  1106.  
  1107. 4.  Make sure you save and restore those registers required by the caller.
  1108.  
  1109.   5.  Be sure to get the right kind of addressibility.    In the FAR call
  1110. example, only CS addresses your segment.  If you are careful with your ASSUME
  1111. statements the assembler will keep track of this fact and generate CS prefixes
  1112. when you make data references; however, you might want to do something like:
  1113.  
  1114.             MOV AX,CS       ;get current segment address
  1115.             MOV DS,AX       ;To DS
  1116.             ASSUME DS:THISSEG
  1117.  
  1118.     Be sure you keep your ASSUMEs in synch with reality.
  1119.  
  1120. Learning about BIOS and the hardware
  1121. ____________________________________
  1122.  
  1123.   You can't do everything with DOS calls.  You may need to learn something
  1124. about the BIOS and about the hardware itself.  In this, the Technical Reference
  1125. is a very good thing to look at.
  1126.  
  1127.   The first thing you look at in the Technical Reference, unless you are really
  1128. determined to master the whole ball of wax, is the BIOS listings presented in
  1129. Appendix A.  Glory be:    here is the whole 8K of ROM which deals with low level
  1130. hardware support layed out with comments and everything.  In fact, if you are
  1131. just interested in learning what BIOS can do for you, you just need to read the
  1132. header comments at the beginning of each section of the listing.
  1133.  
  1134.   BIOS services are invoked by means of the INT instruction; the BIOS occupies
  1135. interrupts 10H through 1FH and also interrupt 5H; actually, of these seventeen
  1136. interrupts, five are used for user exit points or data pointers, leaving twelve
  1137. actual services.
  1138.  
  1139.   In most cases, a service deals with a particular hardware interface; for
  1140. example, BIOS interrupt 10H deals with the screen.  As with DOS function calls,
  1141. many BIOS services can be passed a function code in the AH register and
  1142. possible other arguments.
  1143.  
  1144.   I am not going to summarize the most useful BIOS features here; you will see
  1145. some examples in the next sample program we will look at.
  1146.  
  1147.   The other thing you might want to get into with the Tech reference is the
  1148. description of some hardware options, particularly the asynch adapter, which
  1149. are not well supported in the BIOS.  The writeup on the asynch adapter is
  1150. pretty complete.
  1151.  
  1152.   Actually, the Tech reference itself is pretty complete and very nice as far
  1153. as it goes.  One thing which is missing from the Tech reference is information
  1154. on the programmable peripheral chips on the system board.  These include:
  1155.  
  1156.       the 8259 interrupt controller
  1157.       the 8253 timer
  1158.       the 8237 DMA controller and
  1159.       the 8255 peripheral interface
  1160.  
  1161.   To make your library absolutely complete, you should order the INTEL data
  1162. sheets for these beasts.
  1163.  
  1164.   I should say, though, that the only I ever found I needed to know about was
  1165. the interrupt controller.  If you happen to have the 8086 Family User's Manual,
  1166. the big book put out by INTEL, which is one of the things people sometimes buy
  1167. to learn about 8086 architecture, there is an appendix there which gives an
  1168. adequate description of the 8259.
  1169.  
  1170. A final example
  1171. _______________
  1172.  
  1173.   I leave you with a more substantial example of code which illustrates some
  1174. good elementary techniques; I won't claim its style is perfect, but I think it
  1175. is adequate.  I think this is a much more useful example than what you will get
  1176. with the assembler:
  1177.  
  1178.       PAGE 61,132
  1179.       TITLE SETSCRN -- Establish correct monitor use at boot time
  1180. ;
  1181. ;      This program is a variation on many which toggle the equipment flags
  1182. ;      to support the use of either video option (monochrome or color).
  1183. ;      The thing about this one is it prompts the user in such a way that he
  1184. ;      can select the use of the monitor he is currently looking at (or which
  1185. ;      is currently connected or turned on) without really having to know
  1186. ;      which is which.  SETSCRN is a good program to put first in an
  1187. ;      AUTOEXEC.BAT file.
  1188. ;
  1189. ;      This program is highly dependent on the hardware and BIOS of the IBMPC
  1190. ;      and is hardly portable, except to very exact clones.    For this reason,
  1191. ;      BIOS calls are used in lieu of DOS function calls where both provide
  1192. ;      equal function.
  1193. ;
  1194.  
  1195.   OK.  That's the first page of the program.  Notice the PAGE statement, which
  1196. you can use to tell the assembler how to format the listing.  You give it lines
  1197. per page and characters per line.  I have mine setup to print on the host
  1198. lineprinter; I routinely upload my listings at 9600 baud and print them on the
  1199. host; it is faster than using the PC printer.  There is also a TITLE statement.
  1200. This simply provides a nice title for each page of your listing.  Now for the
  1201. second page:
  1202.  
  1203.       SUBTTL -- Provide .COM type environment and Data
  1204.       PAGE
  1205. ;
  1206. ;      First, describe the one BIOS byte we are interested in
  1207. ;
  1208. BIOSDATA  SEGMENT   AT 40H    ;Describe where BIOS keeps his data
  1209.       ORG        10H       ;Skip parts we are not interested in
  1210. EQUIP      DB        ?          ;Equipment flag location
  1211. MONO      EQU        00110000B ;These bits on if monochrome
  1212. COLOR      EQU        11101111B ;Mask to make BIOS think of the color board
  1213. BIOSDATA  ENDS              ;End of interesting part
  1214. ;
  1215. ;      Next, describe some values for interrupts and functions
  1216. ;
  1217. DOS      EQU        21H       ;DOS Function Handler INT code
  1218. PRTMSG      EQU        09H       ;Function code to print a message
  1219. KBD      EQU        16H       ;BIOS keyboard services INT code
  1220. GETKEY      EQU        00H       ;Function code to read a character
  1221. SCREEN      EQU        10H       ;BIOS Screen services INT code
  1222. MONOINIT  EQU        02H       ;Value to initialize monochrome screen
  1223. ;COLORINIT EQU         03H       ;Value to initialize color screen (80x25)
  1224. COLORINIT EQU       01H         ;Value to initialize color screen (40X25)
  1225. ;
  1226. ;      Now, describe our own segment
  1227. ;
  1228. SETSCRN   SEGMENT          ;Set operating segment for CODE and DATA
  1229. ;
  1230.       ASSUME CS:SETSCRN,DS:SETSCRN,ES:SETSCRN,SS:SETSCRN    ;All segments
  1231. ;
  1232.       ORG        100H      ;Begin assembly at standard .COM offset
  1233. ;
  1234. MAIN      PROC        NEAR      ;COM files use NEAR linkage
  1235.       JMP        BEGIN     ;And, it is helpful to put the data first, but
  1236. ;                  ;then you must branch around it.
  1237. ;
  1238. ;      Data used in SETSCRN
  1239. ;
  1240. CHANGELOC   DD        EQUIP     ;Location of the EQUIP, recorded as far pointer
  1241. MONOPROMPT  DB        'Please press the plus ( + ) key.$'    ;User sees on mono
  1242. COLORPROMPT DB        'Please press the minus ( - ) key.$'   ;User sees on color
  1243.  
  1244.   Several things are illustrated on this page.    First, in addition to titles,
  1245. the assembler supports subtitles:  hence the SUBTTL pseudo-op.    Second, the
  1246. PAGE pseudo-op can be used to go to a new page in the listing.    You see an
  1247. example here of the DSECT-style segment in the "SEGMENT AT 40H".  Here, our our
  1248. interest is in correctly describing the location of some data in the BIOS work
  1249. area which really is located at segment 40H.
  1250.  
  1251.   You will also see illustrated the EQU instruction, which just gives a
  1252. symbolic name to a number.  I don't make a fetish of giving a name to every
  1253. single number in a program.  I do feel strongly, though, that interrupts and
  1254. function codes, where the number is arbitrary and the function being performed
  1255. is the thing of interest, should always be given symbolic names.
  1256.  
  1257.   One last new element in this section is the define doubleword (DD)
  1258. instruction.  A doubleword constant can refer, as in this case, to a location
  1259. in another segment.  The assembler will be happy to use information at its
  1260. disposal to properly assemble it.  In this case, the assembler knows that EQUIP
  1261. is offset 10 in the segment BIOSDATA which is at 40H.
  1262.  
  1263.       SUBTTL -- Perform function
  1264.       PAGE
  1265. BEGIN:      CALL        MONOON            ;Turn on mono display
  1266.       MOV        DX,OFFSET MONOPROMPT      ;GET MONO PROMPT
  1267.       MOV        AH,PRTMSG              ;ISSUE
  1268.       INT        DOS               ;IT
  1269.       CALL        COLORON            ;Turn on color display
  1270.       MOV        DX,OFFSET COLORPROMPT      ;GET COLOR PROMPT
  1271.       MOV        AH,PRTMSG              ;ISSUE
  1272.       INT        DOS               ;IT
  1273.       MOV        AH,GETKEY            ;Obtain user response
  1274.       INT        KBD
  1275.       CMP        AL,'+'                  ;Does he want MONO?
  1276.       JNZ        NOMONO
  1277.       CALL        MONOON            ;yes.  give it to him
  1278. NOMONO:   RET
  1279. MAIN      ENDP
  1280.  
  1281.  
  1282.   The main code section makes use of subroutines to keep the basic flow simple.
  1283. About all that's new to you in this section is the use of the BIOS interrupt
  1284. KBD to read a character from the keyboard.
  1285.  
  1286. Now for the subroutines, MONOON and COLORON:
  1287.  
  1288.       SUBTTL -- Routines to turn monitors on
  1289.       PAGE
  1290. MONOON      PROC        NEAR        ;Turn mono on
  1291.       LES        DI,CHANGELOC    ;Get location to change
  1292.       ASSUME    ES:BIOSDATA     ;TELL ASSEMBLER ABOUT CHANGE TO ES
  1293.       OR        EQUIP,MONO
  1294.       MOV        AX,MONOINIT     ;Get screen initialization value
  1295.       INT        SCREEN        ;Initialize screen
  1296.       RET
  1297. MONOON      ENDP
  1298. COLORON   PROC        NEAR        ;Turn color on
  1299.       LES        DI,CHANGELOC    ;Get location to change
  1300.       ASSUME    ES:BIOSDATA     ;TELL ASSEMBLER ABOUT CHANGE TO ES
  1301.       AND        EQUIP,COLOR
  1302.       MOV        AX,COLORINIT    ;Get screen initialization value
  1303.       INT        SCREEN        ;Initialize screen
  1304.       RET
  1305. COLORON   ENDP
  1306. SETSCRN   ENDS                ;End of segment
  1307.       END        MAIN        ;End of assembly; execution at MAIN
  1308.  
  1309.  
  1310.   The instructions LES and LDS are useful ones for dealing with doubleword
  1311. addresses.  The offset is loaded into the operand register and the segment into
  1312. ES (for LES) or DS (for LDS).  By telling the assembler, with an ASSUME, that
  1313. ES now addresses the BIOSDATA segment, it is able to correctly assemble the OR
  1314. and AND instructions which refer to the EQUIP byte.  An ES segment prefix is
  1315. added.
  1316.  
  1317.   To understand the action here, you simply need to know that flags in that
  1318. particular byte control how the BIOS screen service initializes the adapters.
  1319. BIOS will only work with one adapter at a time; by setting the equipment flags
  1320. to show one or the other as installed and calling BIOS screen initialization,
  1321. we achieve the desired effect.
  1322.  
  1323. The rest is up to you.
  1324. 
  1325. .
  1326.